package com.cppba.alipay.base.request;

import com.cppba.alipay.base.bizcontent.AlipayBizContent;
import com.cppba.alipay.base.domain.constant.AlipayBaseUrl;
import com.cppba.alipay.base.enums.SignEnum;
import com.cppba.alipay.base.exception.AlipayException;
import com.cppba.alipay.base.response.AlipayResponse;
import com.cppba.alipay.util.AlipayJsonUtils;
import com.cppba.alipay.util.HttpClientUtils;
import com.cppba.alipay.util.SignUtils;
import com.google.common.base.Charsets;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝请求父类
 *
 * @author winfed
 * @create 2017-10-18 16:47
 */
@Slf4j
public abstract class AlipayRequest<T extends AlipayResponse> {

    /**
     * 调用结果接受类
     */
    protected Class<T> clazz;

    /**
     * 请求参数存储容器
     */
    private Map<String, Object> parameters;

    /**
     * 接口请求地址
     */
    private String baseUrl;

    /**
     * 应用私钥
     */
    private String privateKey;

    /**
     * 支付宝公钥，验证签名需要
     */
    private String alipayPublicKey;

    /**
     * 应用ID
     */
    private String appId;

    /**
     * 接口方法
     */
    @Getter
    private String method;
    /**
     * 接口中文名
     */
    @Getter
    protected String methodName;

    /**
     * 业务参数
     */
    private AlipayBizContent alipayBizContent;

    /**
     * 签名方式
     */
    private SignEnum signTye;

    /**
     * 构造函数
     *
     * @param method           接口调用方法
     * @param appId            应用ID
     * @param privateKey       应用私钥
     * @param alipayPublicKey  支付宝公钥
     * @param alipayBizContent 业务参数
     */
    public AlipayRequest(String method, String appId, String privateKey, String alipayPublicKey, SignEnum signTye, AlipayBizContent alipayBizContent) {
        this(false, method, appId, privateKey, alipayPublicKey, signTye, alipayBizContent);
    }

    /**
     * @param isSandbox        是否沙箱模式
     * @param method           接口调用方法
     * @param appId            应用ID
     * @param privateKey       应用私钥
     * @param alipayPublicKey  支付宝公钥
     * @param alipayBizContent 业务参数
     */
    public AlipayRequest(Boolean isSandbox, String method, String appId, String privateKey, String alipayPublicKey, SignEnum signTye, AlipayBizContent alipayBizContent) {
        this(AlipayBaseUrl.getBaseUrl(isSandbox), method, appId, privateKey, alipayPublicKey, signTye, alipayBizContent);
    }

    /**
     * @param baseUrl          接口请求地址
     * @param method           接口调用方法
     * @param appId            应用ID
     * @param privateKey       应用私钥
     * @param alipayPublicKey  支付宝公钥
     * @param alipayBizContent 业务参数
     */
    public AlipayRequest(String baseUrl, String method, String appId, String privateKey, String alipayPublicKey, SignEnum signTye, AlipayBizContent alipayBizContent) {
        this.parameters = new HashMap<>();
        // 公共参数
        this.baseUrl = baseUrl;
        this.appId = appId;
        this.method = method;
        this.privateKey = privateKey;
        this.alipayPublicKey = alipayPublicKey;
        this.alipayBizContent = alipayBizContent;
        this.signTye = signTye;
    }

    /**
     * 获取接口请求地址
     *
     * @return
     */
    public String getUrl() {
        return baseUrl;
    }

    /**
     * 增加请求参数
     *
     * @param key   键
     * @param value 值
     */
    public void addParameter(String key, String value) {
        this.parameters.put(key, value);
    }

    /**
     * 刷新请求参数，不签名
     */
    private void refreshParameters() {
        this.refreshParameters(false);
    }

    /**
     * 刷新请求参数
     *
     * @param isSign 是否签名
     */
    private void refreshParameters(Boolean isSign) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        parameters.put("format", "json");
        parameters.put("charset", "utf-8");
        parameters.put("sign_type", signTye.name());
        parameters.put("timestamp", sdf.format(new Date()));
        parameters.put("version", "1.0");
        parameters.put("method", this.method);
        parameters.put("app_id", this.appId);
        String bizContent = "";
        try {
            bizContent = AlipayJsonUtils.beanToJson(alipayBizContent);
        } catch (AlipayException e) {
            log.error("", e);
        }
        this.addParameter("biz_content", bizContent);
        if (isSign) {
            createSign();
        }
    }

    /**
     * 对当前请求参数签名
     */
    private void createSign() {
        // 对请求参数排序拼接成字符串
        String content = SignUtils.parameterText(this.parameters, "&", "sign");
        // 获取签名
        String sign = signTye.createSign(content, privateKey, Charsets.UTF_8.name());
        // 签名拼接
        this.parameters.put("sign", sign);
    }

    /**
     * 获取签名后的请求参数
     *
     * @return 请求参数
     */
    public Map<String, Object> getPreRequestParameters() {
        // 将公共参数放到参数容器中
        refreshParameters(true);
        return this.parameters;
    }

    /**
     * 向支付宝发送请求
     *
     * @return 请求结果
     * @throws AlipayException
     */
    @SuppressWarnings("unchecked")
    protected T sendRequest() throws AlipayException {
        // 将公共参数放到参数容器中
        refreshParameters(true);
        String result = null;
        try {
            result = HttpClientUtils.post(this.getUrl(), this.parameters);
        } catch (IOException e) {
            throw new AlipayException(String.format("调用接口%s失败: [%s]", this.getUrl(), this.parameters));
        }
        // 解析返回参数
        try {
            HashMap resultMap = AlipayJsonUtils.jsonToBean(result, HashMap.class);
            Map realContent = getRealContent(resultMap);
            T t = AlipayJsonUtils.mapToBean(realContent, clazz);
            t.setRequest(this);
            return t;
        } catch (AlipayException e) {
            throw new AlipayException(String.format("%s: [%s]", "返回结构无法解析", result));
        }
    }

    /**
     * 返回的字符串可能还带有签名，需要拨除无用的参数,比如签名
     *
     * @return 有用的返回内容
     */
    private Map getRealContent(Map<String, Object> resultMap) {
        for (Map.Entry o : resultMap.entrySet()) {
            if (o.getValue() instanceof Map) {
                return (Map) o.getValue();
            }
        }
        return null;
    }

    /**
     * 构建请求返回对象
     *
     * @return
     * @throws AlipayException
     */
    public T createResponse() throws AlipayException {
        // 不写成抽象方法是因为有些接口不需要远程调用，比如：统一下单
        throw new AlipayException("该方法需要重写！");
    }

}
